{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# **Ising model solving process using the Light Switch game with Pyqubo**\n",
    "\n",
    "The Ising model is a standard physics model used to solve complicated physics oriented tasks. It is the main solving process used by the python module pyqubo, which we will be using today.\n",
    "\n",
    "The light switch game is a simple game, click [here](https://www.dwavesys.com/tutorials/background-reading-series/quantum-computing-primer#h2-2) for full details on the game, which consists of a set of light switches which have values corresponding to them (weights). If the switch is turned on, the corresponding value is added to the total sum. Hence the goal is to find the minimum value of this sum, by finding an optimal combination of the switches (on or off).\n",
    "\n",
    "\n",
    "**Prerequisites**\n",
    "* Basics of python programming\n",
    "\n",
    "\n",
    "**Dependencies**\n",
    "\n",
    "* pyqubo : simply run ```pip install pyqubo``` on your command line/terminal to install pyqubo.\n",
    "\n",
    "\n",
    "\n",
    "Now we're good to go! Let's begin by importing the 2 required functions from pyqubo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyqubo import Spin, solve_ising"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ising model:\n",
    "\n",
    "Mathematically, the objective function of the Ising model is defined as:\n",
    "\n",
    "$$\\sum_{i}H_i s_i  +  \\sum_{i<j}J_i s_i s_j$$\n",
    "\n",
    "where $s_i, s_j$ represent the spin variables(-1 or 1), and $H_i$ and $J_i$ represent the Ising coefficients. Let's understand what they represent and what their significance is.\n",
    "\n",
    "The spin variables decide on whether the ith item in a certain set of items (which are part of the task) belong to the task or not. For example, if $s_1$ was -1, it would mean the 1st item(in this case, the first switch) would belong to the set, which is saying that it should be turned on.\n",
    "\n",
    "The Ising coefficients are the respective weights attached to each item and the corresponding degree in the objective function. For now we are working with a linear objective function. We'll see more of this in detail further on.\n",
    "\n",
    "The problem now lies in converting the light switch task, into the objective function form. We have seen what the objective function looks like, and it's actually not hard at all to convert the light switch problem to this form. \n",
    "\n",
    "We ignore the second half of the objective function, as we are only dealing with linear terms here. Now, if we see, all the weights attached to the switches become the $H_i$ coefficients, and the switches become the $s_i$ variables.\n",
    "\n",
    "Let us assume we are dealing with 3 switches (x1, x2, x3) having weights 2,1,-3 respectively.\n",
    "\n",
    "The following code sets the switch variables as spin variables (which collapse to either -1 or 1).\n",
    "\n",
    "If you want to work with binary variables(0 or 1), use Binary() instead of Spin() to define the quadratic."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "x1, x2, x3  = Spin(\"x1\"), Spin(\"x2\"), Spin(\"x3\")\n",
    "Quad_form = 2*(x1) + x2 - 3*(x3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now the variable quad_form holds the objective function. This needs to be compiled and converted to an Ising form. We do that with the following set of code."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = Quad_form.compile()\n",
    "deg1, deg2, off = model.to_ising()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "model.to_ising returns the Ising coefficients, which can be used to solve the optimisation task at hand. deg1 holds the coefficients of all the 1st degree terms in the equation, and deg2 holds the 2nd degree terms. off holds the value of any constant that is present in the equation.\n",
    "\n",
    "Since our function is linear, deg2 will be empty, and off will hold no value as there are no constant terms. You can verify this by printing the values of all the coefficients. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Degree1: {'x1': 2.0, 'x2': 1.0, 'x3': -3.0}  Degree2: {}  Offset: 0.0\n"
     ]
    }
   ],
   "source": [
    "print('Degree1:',deg1,' Degree2:',deg2,' Offset:',off)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we finally solve the Ising model by using the solve_ising function, and passing our coefficients to it. Even though deg2 is empty, the function still expects it as an arguement."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'x1': -1, 'x2': -1, 'x3': 1}\n"
     ]
    }
   ],
   "source": [
    "res = solve_ising(deg1,deg2)\n",
    "print(res) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we see, we get the expected output. \n",
    "Experiment with the weights and the number of switches.\n",
    "\n",
    "In case sometimes on running the same code multiple times, different outputs are observed, solve the ising model multiple times and extract the most frequently occuring solution. You can use the following code"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'x1': -1, 'x2': -1, 'x3': 1}\n"
     ]
    }
   ],
   "source": [
    "shots = 10 #variable used to specify number of times you want to solve the model\n",
    "ans = []\n",
    "for i in range(shots):\n",
    "    res = solve_ising(deg1, deg2)\n",
    "    ans.append(res)\n",
    "    \n",
    "result = ans[0]\n",
    "maxim = 0\n",
    "for i in ans:\n",
    "    freq = ans.count(i)\n",
    "    if freq>maxim:\n",
    "        maxim = freq\n",
    "        result = i\n",
    "\n",
    "print(str(result))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we have seen how to formulate a simple Ising model and solve it using pyqubo, you can see how simple it is to solve optimisation tasks using similar principles.\n",
    "Try to solve other optimisation tasks which do not involve constraints, such as the number partitioning problem. Also explore incorporating constraints into the Ising model, for which another tutorial will be available soon! "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
